Skip to content

Fix IllegalArgumentException crash in ScrollView.onTouchEvent on Android#56926

Open
tomekzaw wants to merge 1 commit into
facebook:mainfrom
tomekzaw:fix/android-scrollview-ontouchevent-iae
Open

Fix IllegalArgumentException crash in ScrollView.onTouchEvent on Android#56926
tomekzaw wants to merge 1 commit into
facebook:mainfrom
tomekzaw:fix/android-scrollview-ontouchevent-iae

Conversation

@tomekzaw
Copy link
Copy Markdown
Contributor

@tomekzaw tomekzaw commented May 21, 2026

Summary:

Android's framework ScrollView can throw IllegalArgumentException: pointerIndex out of range from MotionEvent.getY() in ScrollView.onTouchEvent when the view's tracked active pointer becomes stale after a multi-touch ACTION_POINTER_UP.

This is a known Android framework bug

The same exception in sibling widgets (SwipeRefreshLayout, ViewPager) has been reported and worked around with try/catch since at least 2014 — see the Stack Overflow question already linked in the existing in-tree comment, and Google's AOSP Issue #36987494. React Native added the same workaround to ReactScrollView.onInterceptTouchEvent in 67c3ad4 (2018-02-16, originally proposed in #12085 and #13166), with the comment:

} catch (IllegalArgumentException e) {
  // Log and ignore the error. This seems to be a bug in the android SDK and
  // this is the commonly accepted workaround.
  // https://tinyurl.com/mw6qkod (Stack Overflow)
  FLog.w(ReactConstants.TAG, "Error intercepting touch event.", e);
}

The bug is still active on current Android releases — the production stack trace below is from Android 15 (SDK 35) on a Motorola moto g15, May 2026.

The gap this PR closes

ReactScrollView, ReactHorizontalScrollView and ReactNestedScrollView already catch this IllegalArgumentException in onInterceptTouchEvent, but the same catch is missing from onTouchEvent in all three classes, even though super.onTouchEvent(ev) reaches the same framework code path that can throw. This PR mirrors the existing in-file workaround into onTouchEvent.

Production stack trace (Sentry, May 2026)

Device: Motorola moto g15
OS: Android 15 (SDK 35, build VVTA35.51-153)
App stack: React Native 0.85.3 with react-native-gesture-handler

java.lang.IllegalArgumentException: invalid pointerIndex -1 for MotionEvent { action=POINTER_UP(1), id[0]=0, x[0]=591.75, y[0]=153.185, id[1]=1, x[1]=875.5, y[1]=194.935, pointerCount=2, eventTime=15421926904000, downTime=15421677988000, deviceId=12, source=TOUCHSCREEN, displayId=0, eventId=0x38b97d3}
    at android.view.MotionEvent.nativeGetAxisValue(MotionEvent.java)
    at android.view.MotionEvent.getY(MotionEvent.java:2828)
    at android.widget.ScrollView.onTouchEvent(ScrollView.java:941)
    at com.facebook.react.views.scroll.l.onTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:77)
    at android.view.View.performOnTouchCallback(View.java:16474)
    at android.view.View.dispatchTouchEvent(View.java:16426)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3169)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2811)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3197)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at com.swmansion.gesturehandler.react.k.dispatchTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:47)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
    at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:458)
    at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1980)
    at android.app.Activity.dispatchTouchEvent(Activity.java:4579)
    at androidx.appcompat.view.i.dispatchTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:3)
    at io.sentry.android.core.internal.gestures.k.dispatchTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:3)
    at io.sentry.android.core.internal.gestures.i.dispatchTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:40)
    at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:416)
    at android.view.View.dispatchPointerEvent(View.java:16761)
    at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:8145)
    at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:7889)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7281)
    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:7338)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:7304)
    at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:7470)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:7312)
    at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:7527)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7285)
    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:7338)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:7304)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:7312)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7285)
    at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:10505)
    at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:10452)
    at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:10407)
    at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:10670)
    at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:295)
    at android.os.MessageQueue.nativePollOnce(MessageQueue.java)
    at android.os.MessageQueue.next(MessageQueue.java:346)
    at android.os.Looper.loopOnce(Looper.java:191)
    at android.os.Looper.loop(Looper.java:319)
    at android.app.ActivityThread.main(ActivityThread.java:8754)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:962)

The MotionEvent dump pinpoints the trigger: a two-finger ACTION_POINTER_UP(1) where the framework ScrollView's tracked mActivePointerId is stale, so findPointerIndex(...) returns -1 and getY(-1) throws from native code.

Closes #30320
Closes #29642

Both issues were auto-closed by the stale bot without a fix shipping, despite multiple confirmed reproductions over 4+ years.

Changelog:

[ANDROID] [FIXED] - Catch IllegalArgumentException in ScrollView.onTouchEvent to prevent crashes from a known Android framework multi-touch bug

Test Plan:

Can't really reproduce locally — the crash happens very infrequently in production. The Sentry stack trace above is what triggered this PR.

The same IllegalArgumentException thrown by Android's framework ScrollView
when its tracked active pointer becomes stale is already caught in
onInterceptTouchEvent in all three Android ScrollView variants
(ReactScrollView, ReactHorizontalScrollView, ReactNestedScrollView) but
not in onTouchEvent. Mirror the existing workaround into onTouchEvent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 21, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels May 21, 2026
@meta-codesync
Copy link
Copy Markdown

meta-codesync Bot commented May 22, 2026

@Abbondanzo has imported this pull request. If you are a Meta employee, you can view this in D106042303.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: pointerIndex out of range pointerIndex out of range

1 participant